﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace HidApi
{
	public static class HidAPI
	{
		public static int sendReportLength;		// HID下传数据长度
		public static int readReportLength;     // HID上传数据长度
		private const int MaxHidDevices = 64;	// HID最大设备数量

		public static FileStream hidDevice;				// 设备读写流
		public static IntPtr devHandle = IntPtr.Zero;	// 设备句柄
		public static HidAttributes devAttr;			// 设备属性

		/// <summary>
		/// 结构体包含有关HIDClass设备的供应商信息
		/// </summary>
		public struct HidAttributes
		{
			public int Size;
			public ushort VID;			// VendorID
			public ushort PID;			// ProductID
			public ushort VER;          // VersionNumber
		}

		/// <summary>
		/// 结构体包含有关顶级集合的功能的信息。
		/// </summary>
		public struct HIDP_CAPS
		{
			public ushort Usage;
			public ushort UsagePage;
			public ushort InputReportByteLength;
			public ushort OutputReportByteLength;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
			public ushort[] Reserved;
			public ushort NumberLinkCollectionNodes;
			public ushort NumberInputButtonCaps;
			public ushort NumberInputValueCaps;
			public ushort NumberInputDataIndices;
			public ushort NumberOutputButtonCaps;
			public ushort NumberOutputValueCaps;
			public ushort NumberOutputDataIndices;
			public ushort NumberFeatureButtonCaps;
			public ushort NumberFeatureValueCaps;
			public ushort NumberFeatureDataIndices;
		}

		/// <summary>
		/// 获取HID的全局GUID
		/// </summary>
		/// <param name="HidGuid">指向调用者分配的Guid容器</param>
		[DllImport("hid.dll")]
		public static extern void HidD_GetHidGuid(ref Guid HidGuid);

		/// <summary>
		/// 返回HID设备的属性集合
		/// </summary>
		/// <param name="HidDeviceObject">设备句柄，即CreateFile的返回值</param>
		/// <param name="Attributes">返回指定设备的属性集合</param>
		/// <returns>成功返回True，失败返回False</returns>
		[DllImport("hid.dll")]
		public static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HidAttributes attributes);

		/// <summary>
		/// 返回HID顶级集合的预处理数据。
		/// </summary>
		/// <param name="HidDeviceObject">设备句柄，即CreateFile的返回值</param>
		/// <param name="PreparsedData">指向包含_HIDP_PREPARSED_DATA结构中的集合预处理数据的例程分配缓冲区的地址。</param>
		/// <returns>成功返回True，失败返回False</returns>
		[DllImport("hid.dll")]
		public static extern Boolean HidD_GetPreparsedData(IntPtr HidDeviceObject, out IntPtr PreparsedData);

		/// <summary>
		/// 释放资源
		/// </summary>
		/// <param name="PreparsedData"></param>
		/// <returns></returns>
		[DllImport("hid.dll")]
		public static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);

		/// <summary>
		/// 设置HID接收缓冲区大小
		/// </summary>
		/// <param name="HidDeviceObject">设备句柄，即CreateFile的返回值</param>
		/// <param name="NumberBuffers">缓冲区大小参数（65*N）</param>
		/// <returns></returns>
		[DllImport("hid.dll")]
		public static extern Boolean HidD_SetNumInputBuffers(IntPtr HidDeviceObject, int NumberBuffers);
		
		[DllImport("hid.dll")]
		public static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
		
		/// <summary>
		/// 获取已连接电脑的HID设备列表
		/// </summary>
		/// <returns>返回HID设备集合字符串数组</returns>
		public static string[] GetHidDeviceList()
		{
			List<string> deviceList = new List<string>();
			Guid HIDGuid = Guid.Empty;
			HidD_GetHidGuid(ref HIDGuid);
			IntPtr HIDInfoSet = WinAPI.SetupDiGetClassDevs(ref HIDGuid, 0, IntPtr.Zero, WinAPI.DIGCF.DIGCF_PRESENT | WinAPI.DIGCF.DIGCF_DEVICEINTERFACE);    //获取包含所有HID接口信息集合的句柄
			if (HIDInfoSet != IntPtr.Zero)
			{
				WinAPI.SP_DEVICE_INTERFACE_DATA interfaceInfo = new WinAPI.SP_DEVICE_INTERFACE_DATA();
				interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);
				for (uint index = 0; index < MaxHidDevices; index++)
				{
					if (!WinAPI.SetupDiEnumDeviceInterfaces(HIDInfoSet, IntPtr.Zero, ref HIDGuid, index, ref interfaceInfo)) continue;
					int buffsize = 0;
					// 第一次读取接口详细信息(取得信息缓冲区的大小)
					WinAPI.SetupDiGetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
					IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
					WinAPI.SP_DEVICE_INTERFACE_DETAIL_DATA detail = new WinAPI.SP_DEVICE_INTERFACE_DETAIL_DATA()
					{
						cbSize = Marshal.SizeOf(typeof(WinAPI.SP_DEVICE_INTERFACE_DETAIL_DATA))
					};
					Marshal.StructureToPtr(detail, pDetail, false);
					// 第二次读取接口详细信息
					if (WinAPI.SetupDiGetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null))
					{
						deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4)));
					}
					Marshal.FreeHGlobal(pDetail);
				}
			}
			WinAPI.SetupDiDestroyDeviceInfoList(HIDInfoSet);
			return deviceList.ToArray();
		}

		/// <summary>
		/// 连接指定的HID设备
		/// </summary>
		/// <param name="devicePath">目标设备地址</param>
		/// <returns>成功返回True;失败返回False</returns>
		public static Boolean LinkHidDevice(string devPath)
		{
			if (devHandle.ToInt32() > 0) WinAPI.CloseHandle(devHandle);
			devHandle = WinAPI.CreateFile(devPath, WinAPI.Generic.READ | WinAPI.Generic.WRITE, 0, 0, WinAPI.CreationDisposition.OPEN_EXISTING, WinAPI.FileFlag.OVERLAPPED, 0); ;
			if (devHandle == new IntPtr(-1)) return false;

			// 获取设备属性 (若不需要可删除)
			HidD_GetAttributes(devHandle, out devAttr);

			// 找到对应的HID设备信息
			HidD_GetPreparsedData(devHandle, out IntPtr preparseData);
			HidP_GetCaps(preparseData, out HIDP_CAPS caps);
			HidD_FreePreparsedData(preparseData);
			sendReportLength = caps.OutputReportByteLength;
			readReportLength = caps.InputReportByteLength;

			hidDevice = new FileStream(new SafeFileHandle(devHandle, false), FileAccess.ReadWrite, readReportLength, true);
			BeginAsyncRead();
			return true;
		}

		/// <summary>
		/// 开始异步读取数据
		/// </summary>
		private static void BeginAsyncRead()
		{
			byte[] inputBuff = new byte[readReportLength];
			hidDevice.BeginRead(inputBuff, 0, readReportLength, ReadCompleted, inputBuff);
		}

		/// <summary>
		/// 异步读取结束,发出有数据到达事件
		/// </summary>
		/// <param name="iResult">这里是输入报告的数组</param>
		private static void ReadCompleted(IAsyncResult iResult)
		{
			byte[] readBuff = (byte[])(iResult.AsyncState);
			try
			{
				hidDevice.EndRead(iResult);			// 读取结束
				Report e = new Report(readBuff);	// 转存已读取的数据
				OnDataReceived(e);                  // 发出数据到达消息
				BeginAsyncRead();                   // 启动下一次读操作
			}
			//catch (IOException ex)//读写错误,设备已经被移除
			//{
			//	Debug.WriteLine("FileStream读错误 => " + ex.Message);
			//	OnDeviceRemoved(new EventArgs());   // 触发设备移除事件
			//}
			catch (Exception ex)
			{
				Debug.Print("FileStream读错误 => " + ex.Message);
				OnDeviceRemoved(new EventArgs());   // 触发设备移除事件
			}
		}

		/// <summary>
		/// 事件:数据到达,处理此事件以接收输入数据
		/// </summary>
		public static event EventHandler DataReceived;
		public static void OnDataReceived(EventArgs e)
		{
			DataReceived?.Invoke(hidDevice, e);
		}

		/// <summary>
		/// 向HID设备下传数据
		/// </summary>
		/// <param name="byteArray">符合设备数据长度的byte数组,多余的数据将被忽略</param>
		/// <param name="reportID">设备reportID,可忽略为0</param>
		public static void SendBytes(byte[] byteArray, byte reportID = 0)
		{
			byte[] dataBytes = new byte[sendReportLength];
			dataBytes[0] = reportID;
			Array.Copy(byteArray, 0, dataBytes, 1, byteArray.Length > sendReportLength - 1 ? (sendReportLength - 1) : byteArray.Length);
			hidDevice.Write(dataBytes, 0, sendReportLength);
		}
		/// <summary>
		/// 事件:设备断开
		/// </summary>
		public static event EventHandler DeviceRemoved;
		public static void OnDeviceRemoved(EventArgs e)
		{
			DeviceRemoved?.Invoke(hidDevice, e);
		}
		/// <summary>
		/// 遍历并连接第一个匹配的设备
		/// </summary>
		/// <returns>成功返回True,失败返回False</returns>
		public static Boolean LinkOsuKeypad()
		{
			string deviceInfo = "vid_001c&pid_100b&mi_00";
			List<string> keypadDevices = new List<string>();
			foreach (string str in GetHidDeviceList())
			{
				if (str.Contains(deviceInfo))
				{
					keypadDevices.Add(str);
				}
			}
			if (keypadDevices.Count == 0) return false;
			return LinkHidDevice(keypadDevices[0]);
		}
	}
	public class Report : EventArgs
	{
		public readonly byte reportID;
		public readonly byte[] reportData;
		public Report(byte[] reportBuff)
		{
			this.reportID = reportBuff[0];
			List<byte> reportBuffer = new List<byte>(reportBuff);
			reportBuffer.RemoveAt(0);
			this.reportData = reportBuffer.ToArray();
		}
	}
}